
#pragma comment( lib, "glew32.lib" )
//#pragma comment( lib, "freeglut.lib" )
//#pragma comment( lib, "opengl32.lib" )

// Using sprintf instead of the "correct" c++ version ostringstream
#define _CRT_SECURE_NO_DEPRECATE

#include <GL/glew.h>
#include <GL/freeglut.h> // include freeglut before stdlib

#include <stdlib.h>
#include <stdio.h>

#include <time.h>

#include <cmath>

#include "pastycam.h"

namespace{
const int KEY_ID_A = 0;
const int KEY_ID_B = 1;
const int KEY_ID_C = 2;
const int KEY_ID_D = 3;
const int KEY_ID_E = 4;
const int KEY_ID_F = 5;
const int KEY_ID_G = 6;
const int KEY_ID_H = 7;
const int KEY_ID_I = 8;
const int KEY_ID_J = 9;
const int KEY_ID_K = 10;
const int KEY_ID_L = 11;
const int KEY_ID_M = 12;
const int KEY_ID_N = 13;
const int KEY_ID_O = 14;
const int KEY_ID_P = 15;
const int KEY_ID_Q = 16;
const int KEY_ID_R = 17;
const int KEY_ID_S = 18;
const int KEY_ID_T = 19;
const int KEY_ID_U = 20;
const int KEY_ID_V = 21;
const int KEY_ID_W = 22;
const int KEY_ID_X = 23;
const int KEY_ID_Y = 24;
const int KEY_ID_Z = 25;
const int MOUSE_LEFT_BUTTON_DOWN = 26;
const int MOUSE_RIGHT_BUTTON_DOWN = 27;
const int MOUSE_MIDDLE_BUTTON_DOWN = 28;
const int KEY_ID_LEFT = 29;
const int KEY_ID_RIGHT = 30;
const int KEY_ID_UP = 31;
const int KEY_ID_DOWN = 32;
}

namespace global
{
  double lastframetime;
  int window;

  bool keyPressed[200]; // mebe 109 is max. 200 to be save.
  int mouseX, mouseY; 
  int oldMouseX, oldMouseY; 
  int resX, resY;
  PastyCam cam;
  double time;
  
  int fps;
  int frames;
  double fpsTimer;
}

void sleep(int ms)
{
  Sleep(ms);
}

double currentTime()
{
	// <time>'s GetTickCount is choppy. Low resolution.
  //double dTime = GetTickCount() / 1000.0;
  double dTime = float(glutGet(GLUT_ELAPSED_TIME)) / 1000.0;
  return (dTime);
}

float lerp(float x1, float x2, float t){
	return (x2-x1)*t;
}

float rnd()
{
  return float( rand() ) / float(RAND_MAX-1);
}

void renderBitmapString(float x, float y, float z, void *font, char *string) {  
	char *c;
  glRasterPos3f(x, y,z);
  for (c=string; *c != '\0'; c++) {
    glutBitmapCharacter(font, *c);
  }
}


void setOrthographicProjection() {

	// switch to projection mode
	glMatrixMode(GL_PROJECTION);
	// save previous matrix which contains the 
	//settings for the perspective projection
	glPushMatrix();
	// reset matrix
	glLoadIdentity();
	// set a 2D orthographic projection
	gluOrtho2D(0, global::resX, 0, global::resY);
	// invert the y axis, down is positive
	glScalef(1, -1, 1);
	// mover the origin from the bottom left corner
	// to the upper left corner
	glTranslatef(0, -GLfloat(global::resY), 0);
	glMatrixMode(GL_MODELVIEW);
}

void resetPerspectiveProjection() {
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
}


void setupGlew()
{
	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
	  /* Problem: glewInit failed, something is seriously wrong. */
	  fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
	  exit(0);
	}
	fprintf(stdout, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION));
}
void init()
{
  glClearColor(0.0, 0.0, 0.0, 0.0);
  glShadeModel(GL_SMOOTH);
  glEnable(GL_DEPTH_TEST);

  global::lastframetime = currentTime();
  global::fpsTimer = 0;
  global::fps = 0;
  global::frames = 0;
}

void drawGrid()
{
  glBegin(GL_LINES);
	for(int i=-50; i<50; i++)
	{
		glVertex3f((float)i,0,50.0f);
		glVertex3f((float)i+1,0,-50.0f);

		glVertex3f(50.0f,0,(float)i);
		glVertex3f(-50.0f,0,(float)i+1.0f);
	}
  glEnd();
}

void drawText()
{
  setOrthographicProjection();
  glLoadIdentity();

  char buf[100];
  float x = 5.0f;
  float y = 15.0f;
  for(int i=0; i<26; i++)
  {
  	char c = (char)i+65;
	sprintf(buf, "%c", c);
  	if( global::keyPressed[i] )
  	{
  		glColor3ub(255,0,0);
  	}else{
  		glColor3ub(255,255,255);
  	}
  	
	renderBitmapString(x,y,0,GLUT_BITMAP_HELVETICA_18, buf );
  	y += 15.0f;
  	if( y >= float(global::resY) )
  	{
  		y = 15.0f;
  		x += 150;
  	}	
  }
  
  if( global::keyPressed[KEY_ID_LEFT] )
  {
  	sprintf(buf, "<-");
  	renderBitmapString( (float)global::resX/2-15, (float)global::resY/2,0,GLUT_BITMAP_HELVETICA_18,buf);
  }
  if( global::keyPressed[KEY_ID_RIGHT] )
  {
  	sprintf(buf, "->");
  	renderBitmapString( (float)global::resX/2+15, (float)global::resY/2,0,GLUT_BITMAP_HELVETICA_18,buf);
  }
  if( global::keyPressed[KEY_ID_UP] )
  {
  	sprintf(buf, "/|\\");
  	renderBitmapString( (float)global::resX/2, (float)global::resY/2-10,0,GLUT_BITMAP_HELVETICA_18,buf);
  }
  if( global::keyPressed[KEY_ID_DOWN] )
  {
  	sprintf(buf, "\\|/");
  	renderBitmapString( (float)global::resX/2, (float)global::resY/2+10,0,GLUT_BITMAP_HELVETICA_18,buf);
  }
  
  sprintf(buf, "%i fps", global::fps);
  renderBitmapString( (float)global::resX-100, (float)15,0,GLUT_BITMAP_HELVETICA_18,buf);
  
  resetPerspectiveProjection();
}

void calcFps()
{
  if( global::time > global::fpsTimer )
  {
    global::fps = global::frames;
    global::frames = 0;
    global::fpsTimer = global::time + 1.0;
  }
  global::frames++;
}

void display()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  global::time = currentTime();
  float dt = (float)global::time - (float)global::lastframetime;
  global::lastframetime = global::time;
  
  float mouseSpeedX, mouseSpeedY;
  if(global::keyPressed[MOUSE_LEFT_BUTTON_DOWN])
  {
  	float mouseSpd = 0.01f;
  	mouseSpeedX = mouseSpd*float(global::mouseX-global::oldMouseX);
    mouseSpeedY = mouseSpd*float(global::mouseY-global::oldMouseY);
  }
  else
  {
    mouseSpeedX = 0;
    mouseSpeedY = 0;
  }
  global::oldMouseX = global::mouseX;
  global::oldMouseY = global::mouseY;
  
  float f = 5.0f;
  float vx = f * float(global::keyPressed[KEY_ID_LEFT] - global::keyPressed[KEY_ID_RIGHT]) * dt;
  float vz = f * float(global::keyPressed[KEY_ID_UP] - global::keyPressed[KEY_ID_DOWN]) * dt;
  global::cam.update( -vx,
  			  0.0f,
  			  -vz,
  			  -mouseSpeedX,
  			  mouseSpeedY
  );
  
  glLoadMatrixd( global::cam.getMatrix() );
  
  glPushMatrix();
  glTranslatef(0,-10,0);
  drawGrid();
  glPopMatrix();
  
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glShadeModel(GL_FLAT);
  glPushMatrix();
  glTranslatef(0,0,-30);
  glutSolidTeapot(5);
  glPopMatrix();
  glDisable(GL_LIGHTING);
  
  
  drawText();
  calcFps();
  
  glutSwapBuffers();
  glutPostRedisplay();

}

void keyDown(unsigned char key, int x, int y)
{
	switch (key){
	case 'a': global::keyPressed[KEY_ID_A] = true; break;
	case 'b': global::keyPressed[KEY_ID_B] = true; break;
	case 'c': global::keyPressed[KEY_ID_C] = true; break;
	case 'd': global::keyPressed[KEY_ID_D] = true; break;
	case 'e': global::keyPressed[KEY_ID_E] = true; break;
	case 'f': global::keyPressed[KEY_ID_F] = true; break;
	case 'g': global::keyPressed[KEY_ID_G] = true; break;
	case 'h': global::keyPressed[KEY_ID_H] = true; break;
	case 'i': global::keyPressed[KEY_ID_I] = true; break;
	case 'j': global::keyPressed[KEY_ID_J] = true; break;
	case 'k': global::keyPressed[KEY_ID_K] = true; break;
	case 'l': global::keyPressed[KEY_ID_L] = true; break;
	case 'm': global::keyPressed[KEY_ID_M] = true; break;
	case 'n': global::keyPressed[KEY_ID_N] = true; break;
	case 'o': global::keyPressed[KEY_ID_O] = true; break;
	case 'p': global::keyPressed[KEY_ID_P] = true; break;
	case 'q': global::keyPressed[KEY_ID_Q] = true;  
		glutDestroyWindow(global::window); 
		#ifndef _WIN32 
		// Must use this with regular glut, since it never returns control to main().
		exit(0); 
		#endif
	break;
	case 'r': global::keyPressed[KEY_ID_R] = true; break;
	case 's': global::keyPressed[KEY_ID_S] = true; break;
	case 't': global::keyPressed[KEY_ID_T] = true; break;
	case 'u': global::keyPressed[KEY_ID_U] = true; break;
	case 'v': global::keyPressed[KEY_ID_V] = true; break;
	case 'w': global::keyPressed[KEY_ID_W] = true; break;
	case 'x': global::keyPressed[KEY_ID_X] = true; break;
	case 'y': global::keyPressed[KEY_ID_Y] = true; break;
	case 'z': global::keyPressed[KEY_ID_Z] = true; break;
	}
}

void keyUp(unsigned char key, int x, int y)
{
	switch (key){
	case 'a': global::keyPressed[KEY_ID_A] = false; break;
	case 'b': global::keyPressed[KEY_ID_B] = false; break;
	case 'c': global::keyPressed[KEY_ID_C] = false; break;
	case 'd': global::keyPressed[KEY_ID_D] = false; break;
	case 'e': global::keyPressed[KEY_ID_E] = false; break;
	case 'f': global::keyPressed[KEY_ID_F] = false; break;
	case 'g': global::keyPressed[KEY_ID_G] = false; break;
	case 'h': global::keyPressed[KEY_ID_H] = false; break;
	case 'i': global::keyPressed[KEY_ID_I] = false; break;
	case 'j': global::keyPressed[KEY_ID_J] = false; break;
	case 'k': global::keyPressed[KEY_ID_K] = false; break;
	case 'l': global::keyPressed[KEY_ID_L] = false; break;
	case 'm': global::keyPressed[KEY_ID_M] = false; break;
	case 'n': global::keyPressed[KEY_ID_N] = false; break;
	case 'o': global::keyPressed[KEY_ID_O] = false; break;
	case 'p': global::keyPressed[KEY_ID_P] = false; break;
	case 'q': global::keyPressed[KEY_ID_Q] = false; break;
	case 'r': global::keyPressed[KEY_ID_R] = false; break;
	case 's': global::keyPressed[KEY_ID_S] = false; break;
	case 't': global::keyPressed[KEY_ID_T] = false; break;
	case 'u': global::keyPressed[KEY_ID_U] = false; break;
	case 'v': global::keyPressed[KEY_ID_V] = false; break;
	case 'w': global::keyPressed[KEY_ID_W] = false; break;
	case 'x': global::keyPressed[KEY_ID_X] = false; break;
	case 'y': global::keyPressed[KEY_ID_Y] = false; break;
	case 'z': global::keyPressed[KEY_ID_Z] = false; break;
	}
}

void specialKeyUp(int key,int x,int y)
{
  switch(key)
  {
    case  GLUT_KEY_F1                        : break;
    case  GLUT_KEY_F2                        : break;
    case  GLUT_KEY_F3                        : break;
    case  GLUT_KEY_F4                        : break;
    case  GLUT_KEY_F5                        : break;
    case  GLUT_KEY_F6                        : break;
    case  GLUT_KEY_F7                        : break;
    case  GLUT_KEY_F8                        : break;
    case  GLUT_KEY_F9                        : break;
    case  GLUT_KEY_F10                       : break;
    case  GLUT_KEY_F11                       : break;
    case  GLUT_KEY_F12                       : break;
    case  GLUT_KEY_LEFT                      : global::keyPressed[KEY_ID_LEFT] = false; break;
    case  GLUT_KEY_UP                        : global::keyPressed[KEY_ID_UP] = false; break;
    case  GLUT_KEY_RIGHT                     : global::keyPressed[KEY_ID_RIGHT] = false; break;
    case  GLUT_KEY_DOWN                      : global::keyPressed[KEY_ID_DOWN] = false; break;
    case  GLUT_KEY_PAGE_UP                   : break;
    case  GLUT_KEY_PAGE_DOWN                 : break;
    case  GLUT_KEY_HOME                      : break;
    case  GLUT_KEY_END                       : break;
    case  GLUT_KEY_INSERT                    : break;                
  }
}

void specialKeyDown(int key,int x,int y)
{
  switch(key)
  {
    case  GLUT_KEY_F1                        : break;
    case  GLUT_KEY_F2                        : break;
    case  GLUT_KEY_F3                        : break;
    case  GLUT_KEY_F4                        : break;
    case  GLUT_KEY_F5                        : break;
    case  GLUT_KEY_F6                        : break;
    case  GLUT_KEY_F7                        : break;
    case  GLUT_KEY_F8                        : break;
    case  GLUT_KEY_F9                        : break;
    case  GLUT_KEY_F10                       : break;
    case  GLUT_KEY_F11                       : break;
    case  GLUT_KEY_F12                       : break;
    case  GLUT_KEY_LEFT                      : global::keyPressed[KEY_ID_LEFT] = true; break;
    case  GLUT_KEY_UP                        : global::keyPressed[KEY_ID_UP] = true; break;
    case  GLUT_KEY_RIGHT                     : global::keyPressed[KEY_ID_RIGHT] = true; break;
    case  GLUT_KEY_DOWN                      : global::keyPressed[KEY_ID_DOWN] = true; break;
    case  GLUT_KEY_PAGE_UP                   : break;
    case  GLUT_KEY_PAGE_DOWN                 : break;
    case  GLUT_KEY_HOME                      : break;
    case  GLUT_KEY_END                       : break;
    case  GLUT_KEY_INSERT                    : break;                
  }
}

void mousePressed(int button, int state, int posX, int posY)
{
	global::mouseX = posX;
	global::mouseY = posY;
	if(state == GLUT_DOWN)
	{
		switch (button){
		case  GLUT_LEFT_BUTTON: global::keyPressed[MOUSE_LEFT_BUTTON_DOWN] = true; break;    
		case  GLUT_RIGHT_BUTTON: global::keyPressed[MOUSE_RIGHT_BUTTON_DOWN] = true; break;    
		case  GLUT_MIDDLE_BUTTON: global::keyPressed[MOUSE_MIDDLE_BUTTON_DOWN] = true; break;    
		}
	}
	else if(state == GLUT_UP){
		switch (button){
		case  GLUT_LEFT_BUTTON: global::keyPressed[MOUSE_LEFT_BUTTON_DOWN] = false; break;    
		case  GLUT_RIGHT_BUTTON: global::keyPressed[MOUSE_RIGHT_BUTTON_DOWN] = false; break;    
		case  GLUT_MIDDLE_BUTTON: global::keyPressed[MOUSE_MIDDLE_BUTTON_DOWN] = false; break;    
		}	
	}
}

void mouseMoved(int posX, int posY)
{
	global::mouseX = posX;
	global::mouseY = posY;
}

void mousePassive(int posX, int posY)
{
	global::mouseX = posX;
	global::mouseY = posY;
}

void reshape(int w, int h)
{
	global::resX = w;
	global::resY = h;
  glViewport(0, 0, (GLsizei) w, (GLsizei) h); 
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(60.0f, float(w)/float(h) ,1.0f, 3000.0f);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

int main()
{
  int argc;
  char** argv = 0;
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGB);
  global::resX = 700;
  global::resY = 700;
  glutInitWindowSize(global::resX, global::resY); 
  glutInitWindowPosition(10, 10);
  global::window = glutCreateWindow("Simple FreeGlut Window");
  init();
  setupGlew();
  glutKeyboardFunc(keyDown);
  glutKeyboardUpFunc(keyUp);
  glutSpecialFunc(specialKeyDown); // special keys are F1-F12, arrowkeys
  glutSpecialUpFunc(specialKeyUp);
  glutReshapeFunc(reshape);
  glutDisplayFunc(display);
  glutMouseFunc(mousePressed);
  glutMotionFunc(mouseMoved);
  glutPassiveMotionFunc(mousePassive);
  glutMainLoop();
}